﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Kokomo.Environment
{
	/// <summary>
	/// Represents a file type filter.
	/// </summary>
	/// <remarks>
	/// A file type filter examines the content of a file to determine
	/// whether it matches a <see cref="FileType"/>.
	/// </remarks>
	/// <seealso cref="FileTypeHeaderLineFilter"/>
	public abstract class FileTypeFilter
	{
		/// <summary>
		/// The size of the header block provided to the filter.
		/// </summary>
		public const int HeaderBlockSize = 512;

		/// <summary>
		/// Determines the <see cref="FileType"/> of the filter that matches a file.
		/// </summary>
		/// <param name="fileName">The name of the file to check.</param>
		/// <param name="filters">The list of filters to try.</param>
		/// <returns>The <see cref="FileType"/> of the matching filter, if found; otherwise, <c>null</c>.</returns>
		/// <exception cref="ArgumentNullException"><paramref name="fileName"/> is <c>null</c>.</exception>
		/// <exception cref="ArgumentNullException"><paramref name="filters"/> is <c>null</c>.</exception>
		/// <remarks>
		/// The implementation reads the first <see cref="HeaderBlockSize"/> bytes from
		/// the file and iterates through <paramref name="filters"/> until a match
		/// is found.
		/// </remarks>
		public static FileType DetermineFileType(string fileName, IEnumerable<FileTypeFilter> filters)
		{
			if (fileName == null) throw new ArgumentNullException("fileName");
			if (filters == null) throw new ArgumentNullException("filters");

			byte[] header = null;
			header = new byte[FileTypeFilter.HeaderBlockSize];
			File.OpenRead(fileName).Read(header, 0, FileTypeFilter.HeaderBlockSize);

			FileType fileType = null;
			foreach (var filter in filters)
			{
				if (filter.Matches(fileName, header))
				{
					fileType = filter.FileType;
					break;
				}
			}

			return fileType;
		}

		/// <summary>
		/// Initializes a new <see cref="FileTypeFilter"/>.
		/// </summary>
		/// <param name="fileType">The <see cref="FileType"/> associated with the filter.</param>
		protected FileTypeFilter(FileType fileType)
		{
			if (fileType == null) throw new ArgumentNullException("fileType");
			this.FileType = fileType;
		}

		/// <summary>
		/// Gets the <see cref="FileType"/> associated with the filter.
		/// </summary>
		public FileType FileType { get; private set; }
		/// <summary>
		/// Determines whether this filter matches the specified file and header.
		/// </summary>
		/// <param name="fileName">The name of the file.</param>
		/// <param name="header">The header block of the file.</param>
		/// <returns><c>true</c> if the filter matches; otherwise, <c>false</c>.</returns>
		/// <remarks>
		/// Callers should provide a header of at least <see cref="HeaderBlockSize"/> bytes.
		/// </remarks>
		public abstract bool Matches(string fileName, byte[] header);
	}
}
